Skip to main content

Web — Technical Reference

This document covers the technical details for building web Aspects that integrate with the Candescent Digital Banking platform. Web Aspects are JavaScript files injected directly into the OLB DOM, with full access to the page.

For general OIDC concepts (Authorization Code Flow, ID token validation, claims, etc.), see the OIDC Integration Technical Reference.

For the mobile-specific technical reference, see Mobile Technical Reference.


User Context via Global Variables

After a user logs in, the platform exposes session information through the dbk.sessionInfo() API. Your Aspect can read it directly from the DOM.

Available Fields

FieldMethodDescription
User GUIDdbk.sessionInfo().userGuidUnique identifier for the logged-in user
Full Namedbk.sessionInfo().userFullNameDisplay name of the logged-in user

Example

const guid = dbk.sessionInfo().userGuid;
const name = dbk.sessionInfo().userFullName;

console.log(`User: ${name}, GUID: ${guid}`);

Considerations

AdvantageDisadvantage
Fast, synchronous accessData is static and globally accessible in the DOM
No backend call requiredShould not be the sole source of trust for sensitive operations
Automatically cleared on logoutPotentially readable by other scripts on the page

When to use: Display personalization, cross-validation of user-provided information (e.g., verifying identity during chat), and non-security-critical integrations.

Always validate and escape session data before rendering

The dbk.sessionInfo() return value is read from the DOM and must be treated as untrusted input. Aspects authored with the Forge CLI's built-in context-aware templates (welcome-banner, personalized-toast) ship with runtime validation and HTML escaping by default. If you author a custom Aspect:

  • Validate types and length (e.g. userFullName is a string, ≤ 200 chars).
  • Restrict to a character allowlist (Unicode letters, numbers, spaces, .'-).
  • HTML-escape before any innerHTML / template-string DOM insertion.
  • Fall back to a safe default ("Valued Customer") when validation fails.

See Security model below.


User Context via OIDC

For integrations that require secure, verified identity, the platform provides an Aspects-specific OIDC endpoint. This uses the standard OIDC Authorization Code Flow — your Aspect requests an authorization code from the platform, and your backend exchanges it for a token with user claims. This step is identical to the standard OIDC token exchange described in the OIDC Integration Technical Reference.

warning

Never call the Candescent API gateway directly from the front-end Aspect. The Aspect obtains the authorization code; your backend server performs the token exchange.

How It Works

  1. The Aspect (running in the user's browser) calls the Aspects token endpoint to get an authorization code.
  2. The Aspect sends this code to your backend.
  3. Your backend exchanges the code with Candescent's token endpoint for an ID token containing user claims.
  4. Your backend uses the claims to identify the user and respond to the Aspect.

Aspects Token Endpoint

GET https://<FI_DOMAIN>/feng-bff/beta/v1/aspect/token?clientId=<YOUR_CLIENT_ID>
ParameterDescription
FI_DOMAINThe financial institution's branded domain
YOUR_CLIENT_IDYour OIDC consumer/client identifier

Example URL:

https://online.acmebank.com/feng-bff/beta/v1/aspect/token?clientId=YOUR_CLIENT_ID

Request Details

const CLIENT_ID = 'yourOIDCconsumerIdentifier';
const TOKEN_URL = `https://online.acmebank.com/feng-bff/beta/v1/aspect/token?clientId=${CLIENT_ID}`;

fetch(TOKEN_URL, {
method: 'GET',
headers: {
'correlationId': crypto.randomUUID(),
'Cookie': document.cookie,
'Accept': 'application/json'
}
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP status code: ${response.status}`);
}
return response.json();
})
.then(data => {
// Send data.token to your backend for exchange
console.log('Authorization code received. Send to backend for token exchange.');
})
.catch(error => {
console.error(`Error fetching authorization code: ${error.message}`);
});

Backend Token Exchange

Once your backend receives the authorization code, it exchanges it with the centralized Candescent token endpoint. This step is identical to the standard OIDC token exchange described in the OIDC Integration Technical Reference.

Token endpoint:

  • Stage: https://api.candescent.com/digitalbanking/stage/oauth2/v1/token
  • Production: https://api.candescent.com/digitalbanking/prd/oauth2/v1/token

For full details on token exchange requests, ID token validation, and supported claims, see the OIDC Technical Reference.


Web-Specific utilities

Web Aspects have access to the following platform-provided utilities:

dbk.sessionInfo()

Returns user session data as described above. Available only after the user has logged in.

dbk.loadScript(url)

Loads an external JavaScript file asynchronously and returns a Promise that resolves when the script is loaded. Use this to load third-party SDKs.

dbk.loadScript('https://cdn.your-vendor.com/sdk/v3/widget.js').then(function () {
// Vendor SDK is now available
});

Security Best Practices

  • Never expose tokens in the front-end. The Aspect receives an authorization code, not a token. Your backend performs the token exchange.
  • Use HTTPS for all communication.
  • Validate tokens on your backend using the JWKS file provided by your Candescent PM. See ID Token Validation.
  • Global variable data (dbk.sessionInfo()) should be used for display or cross-validation only — not as a trusted identity source for sensitive operations.

Security model

Aspects run inside the FI's banking page with full DOM access, which makes input validation, output encoding, and protocol filtering non-negotiable. Templates generated by forge aspect preview apply the controls below by default; if you hand-author Aspect JavaScript, you must implement the equivalents yourself.

For the trust-zone diagram and party-responsibility table, see the Security Architecture page. For the full questionnaire text + per-template completed responses, see Security Questionnaire (Vendor Response). For pre-deployment review, see the Deployment Security Checklist.

Q-number references

The (Q##) annotations on threat-table rows and subsection headings below map to the JavaScript Aspect Architecture, Security, and Compatibility Questionnaire (full text).

Threats addressed by built-in templates

ThreatWhere it shows upBuilt-in defense
DOM XSS via session context (Q14, Q23)Personalized templates (welcome-banner, personalized-toast) read dbk.sessionInfo().userFullName and previously inserted it into innerHTML directly.Runtime validator (__cdxValidateSession) rejects non-string, oversized, or non-allowlisted names; result is HTML-escaped (__cdxEsc) before render. Falls back to a safe literal if validation fails.
DOM XSS via configured message (Q23)Any template's message parameterServer-side esc() HTML-encodes the literal at generation time, and runtime escape is applied for personalized templates (defense in depth).
javascript: / data: / vbscript: URL injection (Q24)modal template's ctaUrl was assigned to window.location.href without protocol filtering.Runtime validator (__cdxValidateHttpUrl) parses the URL and only returns it when the protocol is http: or https:; otherwise the modal dismisses and an error is logged.
CSP violations from inline event handlers (Q24)All emitted close / dismiss buttons used inline onclick=. Phase 6 finished the rollout by removing the last seven onclick= sites in banner / toast / card / countdown / oidc-snippet (toolkit + production + mobile toasts).Every template now emits data-cdx-action attributes plus addEventListener. The full snippet runs under script-src 'self' with no 'unsafe-inline'.
CSP violations from inline <style> blocks (Q24)Templates inject <style> elements for keyframe animations; these are blocked by style-src 'self'.Optional --csp-nonce <value> flag stamps every emitted <style> element with the host page's per-request nonce so the keyframes run under style-src 'nonce-<value>'. Malformed nonces fall back to no attribute with a console.error (fail-open).
javascript: injection through card.ctaUrl (back-port of Q24)The card template inlined <a href="${ctaUrl}"> directly into HTML — Phase 2's __cdxValidateHttpUrl had only been applied to the modal template.Card now routes ctaUrl through __cdxValidateHttpUrl and sets the href via setAttribute after element creation; rejected protocols silently fall back to #.
Iframe escape / cross-window message forgery / payload injection (Q8 / Q18 / Q19 / Q20)hidden-iframe-sso mounted an unsandboxed iframe and dispatched any {type:'sso-token'} message verbatim.Iframe carries a minimal sandbox attribute; postMessage origin is matched against an inlined allowlist; payload schema is strictly validated; event dispatch uses bubbles:false / composed:false. See SSO iframe hardening.
Hung / flaky network calls (Q30 / Q31 / Q33)oidc-snippet and the mobile-vendor-chat-jsbridge fallback used plain fetch() with no timeout, no retry, and no distinction between transient and client errors.All network templates now route through an inlined __cdxFetch(url, opts, config) helper: AbortController timeout (default 10 s), exponential-backoff retries on 5xx (default 3, capped at 8 s), no retry on 4xx, and a timeout-specific error path. Tunable via --timeout-ms / --max-retries / --retry-delay-ms. See Fetch resiliency.

What this looks like in the emitted snippet

The Forge CLI inlines small validators directly into the generated snippet, so there are no runtime imports or globals required from the FI page:

// Inlined by forge aspect preview --template welcome-banner
function __cdxValidateSession(session) {
if (!session || typeof session !== 'object') return null;
var name = session.userFullName;
if (typeof name !== 'string' || name.length === 0 || name.length > 200) return null;
if (!/^[\p{L}\p{N}\s.'-]+$/u.test(name)) return null;
// ... guid + sessionExpiry checks ...
return { name: name, guid: session.userGuid };
}
function __cdxEsc(str) { /* &<>"'/ → entity refs */ }

var userName = 'Valued Customer';
try {
if (typeof dbk !== 'undefined' && typeof dbk.sessionInfo === 'function') {
var validated = __cdxValidateSession(dbk.sessionInfo());
if (validated) userName = validated.name;
}
} catch (err) {
console.error('[cdx-aspect] welcome-banner: error reading session', err);
}

// Safe to interpolate: both are escaped.
el.innerHTML = '<span>Welcome back, ' + __cdxEsc(userName) + '!</span>';

Authoring custom Aspects

If you write JavaScript that does not go through forge aspect preview, replicate these controls:

  • Treat every value from dbk.sessionInfo() as untrusted. Type-check, length-check, allowlist characters, then escape.
  • Never assign user-controlled URLs to window.location.href without validating the protocol. Use new URL(value, window.location.href) and reject anything other than http: / https:.
  • Use addEventListener instead of inline onclick= / onerror=. This keeps the Aspect compatible with strict CSP (script-src 'self').
  • Fail closed on validation errors. A safe fallback (or a no-op) is preferable to rendering a broken or attacker-influenced UI.

Subresource Integrity for vendor scripts (Q11)

Vendor-loader templates (vendor-script-loader, vendor-script-with-config, tag-manager, vendor-sdk-personalized, mobile-vendor-chat-jsbridge) accept an SRI hash so the FI can pin script content and detect tampered CDNs / MITM substitution.

Generating a hash for your vendor SDK:

Bash
curl -s https://cdn.your-vendor.com/sdk.js \
| openssl dgst -sha384 -binary \
| openssl base64 -A \
| sed 's/^/sha384-/'
# → sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux0SLFR4GWnIyaI8KZRcU7xY7AN5xOo

Passing the hash to a template:

Bash
forge aspect preview --template vendor-script-loader \
--integrity 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux0SLFR4GWnIyaI8KZRcU7xY7AN5xOo' \
--crossorigin anonymous

For vendor-script-with-config, JS and CSS hashes are independent:

Bash
forge aspect preview --template vendor-script-with-config \
--integrity 'sha384-...js-hash...' \
--css-integrity 'sha384-...css-hash...'

What gets emitted:

// In aspectLoadScript(...)
var s = document.createElement('script');
s.src = src;
s.async = true;
s.integrity = 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/ux0SLFR4GWnIyaI8KZRcU7xY7AN5xOo';
s.crossOrigin = 'anonymous';

Important behavior notes:

  • dbk.loadScript is bypassed when SRI is configured. The host shell's loader does not expose integrity, so the template forces a <script> tag (and logs a warning the first time). Without --integrity, dbk.loadScript is preferred when available, preserving the legacy code path.
  • A malformed hash fails open with a console.error. The CLI validates the SRI string (sha{256,384,512}-<base64>, correct base64 length) at template-generation time. If the user provides garbage, the template emits a runtime error log and loads the script without integrity rather than emitting an attribute that would cause a hard load failure.
  • Cache-busting query string is preserved. URLs still get ?v=<timestamp> appended; the SRI hash is on script content, not URL, so this is safe.
  • SRI failure is loud. If the CDN serves content that does not match the hash, the browser fires onerror (the existing partner-script error path) and the FI sees [cdx-aspect] partner SDK failed to load plus the diagnostic logs.

When not to use SRI:

  • The vendor publishes new SDK builds frequently (you would need to refresh the hash on every release). Coordinate a stable release URL with the vendor first.
  • Loaders that internally fetch additional dynamic assets — SRI only protects the top-level script, not transitive imports.

SSO iframe hardening (Q8 / Q18 / Q19 / Q20)

The hidden-iframe-sso template mounts a 0×0 iframe that posts a session token back to the parent page via postMessage. Three classes of attack apply: (1) compromised iframe escaping its sandbox, (2) untrusted windows posting forged tokens, (3) malformed payloads injecting unexpected fields into the listener. The CLI defends against all three by default.

1. Iframe sandbox (Q8). The emitted iframe carries sandbox="allow-same-origin allow-scripts" — the minimum required for the SSO endpoint to run JS and call postMessage back to the parent. Override via --iframe-sandbox:

Bash
# Most-restrictive sandbox (vendor needs to be no-script tolerant)
forge aspect preview --template hidden-iframe-sso --iframe-sandbox ''

# Vendor needs forms (e.g. multi-step OIDC consent screens)
forge aspect preview --template hidden-iframe-sso \
--iframe-sandbox 'allow-same-origin allow-scripts allow-forms'

The CLI validates sandbox tokens against the WHATWG list; unknown tokens fall back to the default with a console.error.

2. postMessage origin allowlist (Q19). The iframe's own origin is always trusted. Use --allowed-origins (comma- or whitespace-separated) when the SSO endpoint posts back from a federated origin (e.g. an IdP redirect chain):

Bash
forge aspect preview --template hidden-iframe-sso \
--iframe-src 'https://sso.bank.example.com/handoff' \
--allowed-origins 'https://idp.partner.com, *.federation.example.com'

Wildcards take the form *.domain.tld (subdomain match). At runtime, the listener calls an inlined __cdxValidateMessageOrigin(event.origin, allowlist) helper; messages from other origins are dropped with a console.warn.

3. Message schema validation (Q18). Tokens are validated against a strict schema before dispatch:

FieldRequired?Validation
typeyesexactly 'sso-token'
tokenyesnon-empty string, ≤ 4096 chars
expirynonumber; must be a future Unix-ms timestamp
signaturenonon-empty string, ≤ 1024 chars

Any other fields on the payload are stripped — the dispatched event.detail contains only {type, token, expiry?, signature?}. A compromised SSO endpoint cannot use the channel to inject arbitrary properties into the listener.

4. Event-dispatch isolation (Q20). The custom cdx-aspect:sso-token event is dispatched with bubbles: false and composed: false, so it does not cross shadow-DOM boundaries or trigger unrelated listeners on the document tree.

What is not yet automated. HMAC signature verification (the signature field is type-checked but not cryptographically validated against an FI-provided key) is tracked under a later phase; FIs that ship signed tokens should validate them in the listener that consumes cdx-aspect:sso-token.

Fetch resiliency for OIDC + mobile bridge (Q30 / Q31 / Q33)

The two templates that perform network requests (oidc-snippet and the JSBridge fallback in mobile-vendor-chat-jsbridge) wrap every fetch call in an inlined __cdxFetch(url, options, config) helper that adds:

  • AbortController-based timeout — defaults to 10 s. Override via --timeout-ms <1000-60000>.
  • Retry with exponential backoff on 5xx and network errors — defaults to 3 retries with initial 1 s delay. Override via --max-retries <0-5> and --retry-delay-ms <100-10000>. Each subsequent attempt is capped at 8 s of backoff.
  • No retry on 4xx — client errors are surfaced immediately so misconfigured clientId / fiDomain fail fast.
  • Timeout-specific error pathAbortError is rewritten to Error('Request timed out after <ms>ms') so the user-facing toast and console.error show an actionable message instead of "AbortError".

Configuration examples:

Bash
# Tight timeout for a fast-responding OIDC endpoint, no retries.
forge aspect preview --template oidc-snippet \
--client-id my-app --fi-domain acmebank \
--timeout-ms 5000 --max-retries 0

# Generous timeout + extra retries for a slow staging environment.
forge aspect preview --template oidc-snippet \
--client-id my-app --fi-domain acmebank \
--timeout-ms 30000 --max-retries 5 --retry-delay-ms 2000

Validation behavior at generation time. Out-of-range or non-numeric values are dropped silently and the template falls back to the runtime defaults — the CLI never emits a snippet that would crash because of a malformed flag. Ranges:

FlagRangeDefault
--timeout-ms1 000 – 60 000 ms10 000
--max-retries0 – 53
--retry-delay-ms100 – 10 000 ms1 000

The runtime contract (visible in the emitted snippet):

__cdxFetch(authUrl, {method: 'GET', credentials: 'include'}, {timeoutMs: 15000, maxRetries: 2})
.then((resp) => resp.json())
.then(/* success path */)
.catch((err) => {
// err.message starts with "Request timed out after" on AbortError,
// "HTTP 4xx ..." on client errors, or the original error message otherwise.
});

The error toast in oidc-snippet distinguishes timeouts from other failures and suggests --timeout-ms so integrators can tune for slow environments.

CSP nonce for inline <style> (Q24)

Templates inject <style> elements at runtime for the keyframe animations that drive the slide-in / fade-in effects. Under a strict style-src 'self' CSP these would be blocked. Pass --csp-nonce <value> to attach the FI's per-request nonce to every emitted <style>:

Bash
forge aspect preview --template banner --csp-nonce "$REQUEST_NONCE"

The CLI validates the nonce at generation time:

  • Must be a string with no whitespace, quotes, angle brackets, or backticks (anything that would break HTML attribute serialization).
  • Length 1 – 256 characters.
  • Anything else is rejected at runtime with a console.error and the template falls back to omitting the nonce attribute (fail-open — the FI's CSP report-only logs will surface it).

Templates affected: banner, toast, card, modal, countdown, welcome-banner, personalized-toast, oidc-snippet (all three toast variants). Templates that don't emit inline <style> (vendor-script-loader, hidden-iframe-sso, etc.) accept the flag but don't apply it.

What gets emitted with --csp-nonce abc-123:

const style = document.createElement('style');
style.setAttribute('nonce', 'abc-123');
style.textContent = '@keyframes cdxSlideIn{...}';
document.head.appendChild(style);

Correlation IDs for incident response (Q35)

Three templates that cross trust boundaries — oidc-snippet, mobile-vendor-chat-jsbridge, hidden-iframe-sso — bake a correlation id into every console.* line they emit. The id is auto-generated at template-generation time (format: aspect_<base36-timestamp>_<base64url-random>), so each forge aspect preview / forge aspect submit produces a snippet with a stable, unique tag for the lifetime of that deployment.

What it looks like in the browser console:

[cdx-aspect:aspect_lt6m8n2p_xY9zKqAB] OIDC authorization code obtained
[cdx-aspect:aspect_lt6m8n2p_xY9zKqAB] Send this code to your backend for token exchange
[cdx-aspect:aspect_lt6m8n2p_xY9zKqAB] Code: abc123…
[cdx-aspect:aspect_lt6m8n2p_xY9zKqAB] Redirect URL:

Every log line in the same snippet shares the same id, so an FI on-call engineer searching browser logs after an incident can confidently attribute a sequence of events to a single Aspect instance.

Chaining with your CI / release pipeline. Pass --correlation-id <value> to override the auto-generated id and stitch logs across systems:

Bash
# CI sets RELEASE_ID=release-2026.05.08.42
forge aspect submit "auth-handoff" --template oidc-snippet \
--client-id my-app --fi-domain acmebank \
--correlation-id "$RELEASE_ID" \
--platform web --wait

Now any browser log line from this deployment carries [cdx-aspect:release-2026.05.08.42], which can be cross-referenced against your release tracking, Sentry events, or vendor support tickets.

Validation rules (so a malformed id can't smuggle quotes/whitespace into the emitted log strings):

  • 4 – 128 characters.
  • Alphanumerics plus ., -, _ only.
  • Anything else is silently replaced by the auto-generated default at generation time. (The CLI flag rejects bad input upstream; this fallback is defense-in-depth for direct API users.)

Templates outside this scope (banner, toast, card, modal, countdown, welcome-banner, personalized-toast, vendor-loaders) keep the legacy [cdx-aspect] prefix without a per-instance id. They don't cross trust boundaries or call external services, so correlation IDs would be noise.

dbk API contract

Aspects integrate with the Digital Banking host shell through a small window.dbk API. The contract is intentionally minimal so that aspects degrade gracefully when running outside the host (e.g. local mock dashboard, mobile WebView preview, older OLB builds). Templates emitted by the Forge CLI always check for availability before use.

MethodReturnsAvailabilityFalls back to
dbk.sessionInfo(){userFullName, userGuid, sessionExpiry?, signature?}Web (post-login), Mobile WebView (after apiToken:ready)Generic greeting / anonymous identity. Validated by the Forge CLI's __cdxValidateSession helper before use.
dbk.loadScript(url)Promise<void>Web only (some older OLB builds expose it)Plain <script> tag with the same URL. Bypassed when SRI is configured (host loader can't apply integrity).
dbk.isWebview()booleanMobile WebViews onlyTreated as false on web. Used by hidden-iframe-sso --webview-gate.
window.webkit.messageHandlers.tokenApiDetails (iOS native bridge)n/a (one-way postMessage)iOS WebView onlyFalls through to JSBridge then to fetch against the configured --token-endpoint.
window.JSBridge.tokenApiDetails(payload) (Android native bridge)n/aAndroid WebView onlySame fallback as above.

Security properties of the contract:

  • dbk.sessionInfo() is untrusted by default. Returned values are validated against a strict schema (string, ≤ 200 chars for userFullName, Unicode-letter allowlist) and HTML-escaped before any DOM insertion. See Security model.
  • dbk.loadScript() does not support SRI. When --integrity is supplied, templates bypass dbk.loadScript and fall back to <script> tags with the integrity attribute. See Subresource Integrity.
  • JSBridge payloads are validated like postMessage data. The mobile token response is treated as external input — type-checked and length-bounded before use.

Versioning. The dbk contract is stable across the Aspects template catalog. Breaking changes (e.g. removing or renaming a method) would require coordinated rollout with FI host shells; the Forge CLI checks for typeof dbk !== 'undefined' && typeof dbk.<method> === 'function' before every call so an absent method results in graceful degradation, not a runtime error.

What is not yet covered

The following are tracked under later phases of the security plan and are not enforced by the current templates:

  • Shadow DOM CSS isolation for UI templates.
  • HMAC signature verification on the SSO signature field (currently type-checked only).
  • A consolidated security-architecture diagram + FI deployment checklist (Phase 8 deliverable).

Until those phases land, FI security teams reviewing aspect deployments should validate these controls manually — see the FAQ for the current checklist.


Next Steps